package com.mathdroid.gui.main;

import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.concurrent.ExecutionException;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.WindowConstants;

import com.mathdroid.exceptionhandling.ExceptionHandler;
import com.mathdroid.gui.ImageLoader;
import com.mathdroid.gui.main.talents.TalentButton;
import com.mathdroid.model.Model;

/**
 * This is shown when an SwingWorker thread does his work in the background.
 * After the work is done, the results can be shown in an JPanel. This has
 * to be added to the Task as a PropertyChangeListener.
 * @param <T> The return value class of the SwingWorker.
 * @param <U> The return value of interim results of the SwingWorker.
 */
public abstract class AOptimizerFrame<T, U> 
extends JFrame 
implements PropertyChangeListener {

	/**
	 * Default UID.
	 */
	private static final long serialVersionUID = 1L;
	
	/**
	 * The progress bar shows the progress of the Task.
	 */
	private JProgressBar progress = null;
	
	/**
	 * Button to abort the Task.
	 */
	private final JButton abort = new JButton("Abort");
	
	/**
	 * Constraints for this UI components.
	 */
	private final GridBagConstraints c = new GridBagConstraints();
	
	/**
	 * The main panel.
	 */
	private JPanel p = new JPanel();
	
	/**
	 * The content added by extending classes.
	 */
	private JPanel content = new JPanel();
	
	/**
	 * The weight of big components.
	 */
	private static final int HIGH_WEIGHT = 10;
	
	/**
	 * The task of this frame.
	 */
	private SwingWorker<T, U> task = null;

	/**
	 * ImageLoader.
	 */
	private final ImageLoader il = new ImageLoader();
	
	/**
	 * Create a new optimizer view.
	 * @param t The task to get progress information from.
	 * @param min Minimum value of the progress bar.
	 * @param max Maximum value of the progress bar.
	 * @param title The title of this frame.
	 */
	public AOptimizerFrame(
			final SwingWorker<T, U> t, final int min, final int max, 
			final String title) {
		super(title);
		this.task = t;
		this.progress = new JProgressBar(min, max);
		
		this.p.setLayout(new GridBagLayout());
		this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
		this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
		
		this.add(this.p);
		
		this.buildProgressBar();
		this.buildAbortButton();
		
		this.pack();
		
		this.center();
		
		this.setIconImage(new ImageIcon(
				TalentButton.class.getClass().getResource(
				"/com/mathdroid/gui/images/frameicon.png")).getImage());
		
		this.addWindowListener(
				new OptimizerWindowListener<T, U>(this.task, this));
		this.setVisible(true);
	}

	/**
	 * Build the "abort" button.
	 */
	private void buildAbortButton() {
		this.c.gridx = 1;
		this.c.gridy = 0;
		this.c.weightx = 0;
		this.c.weighty = 0;
		this.c.gridwidth = 1;
		this.c.fill = GridBagConstraints.NONE;
		
		this.abort.setIcon(this.il.getImage("cancel"));
		
		this.abort.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(final ActionEvent e) {
				task.cancel(true);
				dispose();
			}
		});
		
		this.p.add(this.abort, this.c);
	}

	/**
	 * Show this frame in the center of the DroidFrame (this is the main frame).
	 */
	private void center() {
		final int width = this.getWidth();
		final int height = this.getHeight();
		DroidFrame f = Model.getDroidFrame();
		
		final int x = (int) f.getLocationOnScreen().getX() 
				+ f.getWidth() / 2 - width / 2;
		final int y = (int) f.getLocationOnScreen().getY() 
				+ f.getHeight() / 2 - height / 2;
		
		this.setBounds(x, y, width, height);
	}

	/**
	 * Build the progress bar.
	 */
	private void buildProgressBar() {
		final int width = 400;
		final int height = 25;
		
		this.c.gridx = 0;
		this.c.gridy = 0;
		this.c.weightx = HIGH_WEIGHT;
		this.c.weighty = 0;
		this.c.gridwidth = 1;
		this.c.fill = GridBagConstraints.HORIZONTAL;
		
		this.progress.setIndeterminate(false);
		this.progress.setStringPainted(true);
		this.progress.setPreferredSize(new Dimension(width, height));
		
		this.p.add(this.progress, this.c);
	}
	
	/**
	 * Call this method to get the content of this frame. The content is shown
	 * after the SwingWorker Task is done. So the content panel shows the
	 * results of the task.
	 * @return Content panel.
	 */
	public final JPanel getContent() {
		return this.content;
	}

	@Override
	public final void propertyChange(final PropertyChangeEvent e) {
		if (e.getPropertyName().toString().equals("state") 
				&& e.getNewValue().toString().equals("DONE")) {
			this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
			
			try {
				if (!this.task.isCancelled()) {
					this.done(this.task.get());
					
					this.c.gridx = 0;
					this.c.gridy = 1;
					this.c.weightx = HIGH_WEIGHT;
					this.c.weighty = HIGH_WEIGHT;
					this.c.gridwidth = 2;
					this.c.fill = GridBagConstraints.BOTH;
					this.p.add(this.content, this.c);
					
					this.abort.setEnabled(false);
					
					this.p.remove(this.abort);
					this.p.remove(this.progress);
				} else {
					this.dispose();
				}
			} catch (InterruptedException ex) {
				ExceptionHandler.report("InterruptedException", ex);
			} catch (ExecutionException ex) {
				ExceptionHandler.report("ExecutionException", ex);
			}
			
			this.pack();
			this.center();
		}
		
		if ("progress".equals(e.getPropertyName())) {
			this.progress.setValue(this.task.getProgress());
		}		
	}
	
	/**
	 * This method is called after the SwingWorker task did it's work. This
	 * method is invoked before laying the "result" UI components out.
	 * @param result Result of the SwingWorker.
	 */
	protected abstract void done(T result);
	
	/**
	 * @return ImageLoader.
	 */
	public final ImageLoader getImageLoader() {
		return this.il;
	}
	
	/**
	 * @return The task.
	 */
	public final SwingWorker<T, U> getTask() {
		return task;
	}
}
